package csm.common.thread;

import java.util.Vector;

/**
 * {@link Task}들의 목록을 내부적으로 가지고, 각 {@link Task}별로 설정된 주기대로 실행을 관리한다. <br>
 * 사실상 {@link Task} 시리즈 클래스에서 가장 복잡하고, 가장 핵심이 되는 부분이다.
 *
 * @author seungmun
 * @since 2006.1.18.
 * @see Task
 */
class TaskTimer extends Thread
{
    /**
     * 이 스레드의 실행을 탈출하기 위한 플래그.(True:작업을 중지하고, 탈출한다.)
     */
    private static boolean      done  = true;

    /**
     * 스케줄링할 작업이 저장되는 저장소(원소를 꺼내면, 저장소에서는 제거되는 특성을 가진 컬렉션을 사용해야 한다)
     */
    private static Vector<Task> tasks = null;
    private static TaskTimer    timer = null;

    /**
     * 주어진 Task를 스케줄러에 등록한다.
     *
     * @param task
     */
    public static synchronized void add(Task task)
    {
        // 다음 실행시점을 얻는다.
        task.nextRun = System.currentTimeMillis() + task.msPeriod;

        // Task저장소를 확보한다.
        if (tasks == null)
            tasks = new Vector<Task>();

        // 주어진 Task를 저장소에 저장한다.
        tasks.addElement(task);

        // 저장된 Task들을 작업순서별로 재배치한다.
        sortTasks(tasks);

        // 스케쥴러 객체를 확보한다.
        if (timer == null)
        {
            done = false;
            timer = new TaskTimer();
        }
    }

    /**
     * 스케줄러에서 Task를 제거한다. <br>
     * Task저장소에 더이상 Task가 없으면, 현재 확보된 스케줄러 자체도 해제한다.
     *
     * @param task
     */
    public static synchronized void deschedule(Task task)
    {
        // 지정한 Task의 실행자의 작업이 끝났음을 시스템에게 알린다.
        task.runner.done = true;
        task.runner.notifier();

        // 저장소에서 제거.
        tasks.removeElement(task);

        // 저장소에 더이상 Task가 없으면 스케줄러 자원도 해제한다.
        if (tasks.size() == 0)
        {
            // 스케줄러의 작업이 끝났음을 시스템에게 알린다.
            done = true;
            tasks = null;
            timer.notifier();
            timer = null;
        }
    }

    /**
     * 스케줄러 생성자.
     */
    public TaskTimer()
    {
        this.setPriority(Thread.MAX_PRIORITY);
        this.start();
    }

    /**
     * 스케줄러를 시작했을때 수행되는 작업.
     */
    public synchronized void run()
    {
        while (true)
        {
            // 스케줄러의 작업이 더이상 없을 경우, 탈출한다.
            if (done)
                break;

            // 스케줄링할 작업을 꺼낸다.
            Task nextTask = (Task) tasks.elementAt(0);

            // 꺼낸 작업의 예약시간을 저장한다.
            long nextRun = nextTask.nextRun;

            try
            {
                // 작업자체가 정렬되어 저장되어 있기때문에 가장 먼저 꺼낸 작업이
                // 실행시점이 되질 않았다면 전체를 기다려야 한다.
                wait(nextRun - System.currentTimeMillis());
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }

            if (done)
                break;

            // 드디어 실행 시점이 되었다.

            // 현재 실행 시간을 저장한다.
            long now = System.currentTimeMillis();

            // 저장소에 저장된 각각의 작업을 실행한다.
            for (int i = 0; i < tasks.size(); ++i)
            {
                Task task = (Task) tasks.elementAt(i);

                if (task.nextRun > now)
                    break;

                task.runner.notifier();

                task.nextRun = System.currentTimeMillis() + task.msPeriod;
            }
            sortTasks(tasks);
        }
    }

    /**
     * 버블소트를 이용한다.ㅡ.ㅡ
     *
     * @param tasks
     */
    private static void sortTasks(Vector<Task> tasks)
    {
        for (int i = 0; i < tasks.size() - i; ++i)
        {
            Task taski = (Task) tasks.elementAt(i);
            for (int j = i + 1; j < tasks.size(); ++j)
            {
                Task taskj = (Task) tasks.elementAt(j);
                if (taskj.nextRun < taski.nextRun)
                {
                    tasks.setElementAt(taskj, i);
                    tasks.setElementAt(taski, j);
                    taski = taskj;
                }
            }
        }
    }

    public synchronized void notifier()
    {
        notify();
    }

}
